Setup

Welcome to another tutorial for this class, COMP/STAT 112: Introduction to Data Science! It will be similar to the others, including demo videos and files embedded in this document and practice problems with hints or solutions at the end. There are some new libraries, so be sure to install those first.

As most of our files do, we start this one with three R code chunks: 1. options, 2. libraries and settings, 3. data.

knitr::opts_chunk$set(echo = TRUE, 
                      message = FALSE, 
                      warning = FALSE)
library(tidyverse)     # for data cleaning and plotting
library(googlesheets4) # for reading googlesheet data
library(lubridate)     # for date manipulation
library(openintro)     # for the abbr2state() function
library(palmerpenguins)# for Palmer penguin data
library(maps)          # for map data
library(ggmap)         # for mapping points on maps
library(gplots)        # for col2hex() function
library(RColorBrewer)  # for color palettes
library(sf)            # for working with spatial data
library(leaflet)       # for highly customizable mapping
library(ggthemes)      # for more themes (including theme_map())
library(plotly)        # for the ggplotly() - basic interactivity
library(gganimate)     # for adding animation layers to ggplots
library(shiny)         # for creating interactive apps
gs4_deauth()           # To not have to authorize each time you knit.
theme_set(theme_minimal())
# Lisa's garden data
garden_harvest <- read_sheet("https://docs.google.com/spreadsheets/d/1DekSazCzKqPS2jnGhKue7tLxRU3GVL1oxi-4bEM5IWw/edit?usp=sharing") %>% 
  mutate(date = ymd(date))

Learning Goals

After this tutorial, you should be able to do the following:

  • Add basic interactivity to a ggplot2 plot using ggplotly().

  • Add animation layers to plots using gganimate functions.

  • Create a shiny app that requires inputs.

  • Publish a shiny app to shinyapps.io.

Motivation

Easy interactivity with plotly

Probably the easiest way to add interactivity to a plot created with ggplot2 is by using the ggplotly() function from the plotly library. The plotly package can do A LOT more than what we’ll cover in this course as it is a plotting framework if its own. But, it can do a lot with just that one function.

Let’s look at an example. In the code below, I compute the cumulative harvest in pounds by vegetable and create a bar graph. I save the graph and print it out. The code and graph should be familiar.

veggie_harvest_graph <- garden_harvest %>% 
  group_by(vegetable) %>% 
  summarize(total_wt_lbs = sum(weight)*0.00220462) %>% 
  ggplot() +
  geom_col(aes(x = total_wt_lbs, 
               y = fct_reorder(vegetable, 
                               total_wt_lbs, 
                               .desc = FALSE))) +
  labs(title = "Total Harvest by vegetable (lb)", 
       x = "",
       y = "")

veggie_harvest_graph

Now, we ploty-ify it!

ggplotly(veggie_harvest_graph)

The labeling is fairly ugly in the graph above. I can fix some of that by editing my original plot. In the code below, I add a text aesthetic, which will be used in ggplotly() to display the vegetable name, and use tooltip to tell it the aesthetics to display when scrolling over the graph.

veggie_harvest_graph2 <- garden_harvest %>% 
  group_by(vegetable) %>%
  summarize(total_wt_lbs = sum(weight)*0.00220462) %>%
  ggplot() +
  geom_col(aes(x = total_wt_lbs,
               y = fct_reorder(vegetable,
                               total_wt_lbs,
                               .desc = FALSE),
               text = vegetable)) +
  labs(title = "Total Harvest by vegetable (lb)",
       x = "",
       y = "")

ggplotly(veggie_harvest_graph2,
         tooltip = c("text", "x"))

This works for many different types of plots created with ggplot2.

Your turn!

Exercise

In this exercise, choose 2 graphs you have created for ANY assignment in this class and add interactivity using the ggplotly() function.

Adding animation with gganimate

Key functions

The gganimate package works well with ggplot2 functions by providing additional grammar that assists in adding animation to the plots. These functions get added as layers in ggplot(), just like you are used to adding geom_*() layers and other layers that modify the graph.

From Thomas Pedersen’s documentation, here are the key functions/grammar of the package:

  • transition_*() defines how the data should be spread out and how it relates to itself across time (time is not always actual time).
  • view_*() defines how the positional scales should change along the animation.
  • shadow_*() defines how data from other points in time should be presented in the given point in time.
  • enter_*()/exit_*() defines how new data should appear and how old data should disappear during the course of the animation.
  • ease_aes() defines how different aesthetics should be eased during transitions.

You only need a transition_*() or view_*() function to add animation. This tutorial focuses on three transition_*() functions: transition_states(), transition_time(), and transition_reveal().

transition_*() functions

The following image, taken from the gganimate cheatsheet, gives a nice overview of the three functions.

Demo video

Resources

Your turn!

Creating an app with shiny

Demo video

Resources

Your turn!

LS0tCnRpdGxlOiAiQ3JlYXRpbmcgaW50ZXJhY3RpdmUgYW5kIGFuaW1hdGVkIHBsb3RzIGluIFIiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBkZl9wcmludDogcGFnZWQKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKLS0tCgojIyBTZXR1cAoKV2VsY29tZSB0byBhbm90aGVyIHR1dG9yaWFsIGZvciB0aGlzIGNsYXNzLCBDT01QL1NUQVQgMTEyOiAqSW50cm9kdWN0aW9uIHRvIERhdGEgU2NpZW5jZSohIEl0IHdpbGwgYmUgc2ltaWxhciB0byB0aGUgb3RoZXJzLCBpbmNsdWRpbmcgZGVtbyB2aWRlb3MgYW5kIGZpbGVzIGVtYmVkZGVkIGluIHRoaXMgZG9jdW1lbnQgYW5kIHByYWN0aWNlIHByb2JsZW1zIHdpdGggaGludHMgb3Igc29sdXRpb25zIGF0IHRoZSBlbmQuIFRoZXJlIGFyZSBzb21lIG5ldyBsaWJyYXJpZXMsIHNvIGJlIHN1cmUgdG8gaW5zdGFsbCB0aG9zZSBmaXJzdC4KCkFzIG1vc3Qgb2Ygb3VyIGZpbGVzIGRvLCB3ZSBzdGFydCB0aGlzIG9uZSB3aXRoIHRocmVlIFIgY29kZSBjaHVua3M6IDEuIG9wdGlvbnMsIDIuIGxpYnJhcmllcyBhbmQgc2V0dGluZ3MsIDMuIGRhdGEuIAoKYGBge3Igc2V0dXB9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFKQpgYGAKCmBgYHtyIGxpYnJhcmllc30KbGlicmFyeSh0aWR5dmVyc2UpICAgICAjIGZvciBkYXRhIGNsZWFuaW5nIGFuZCBwbG90dGluZwpsaWJyYXJ5KGdvb2dsZXNoZWV0czQpICMgZm9yIHJlYWRpbmcgZ29vZ2xlc2hlZXQgZGF0YQpsaWJyYXJ5KGx1YnJpZGF0ZSkgICAgICMgZm9yIGRhdGUgbWFuaXB1bGF0aW9uCmxpYnJhcnkob3BlbmludHJvKSAgICAgIyBmb3IgdGhlIGFiYnIyc3RhdGUoKSBmdW5jdGlvbgpsaWJyYXJ5KHBhbG1lcnBlbmd1aW5zKSMgZm9yIFBhbG1lciBwZW5ndWluIGRhdGEKbGlicmFyeShtYXBzKSAgICAgICAgICAjIGZvciBtYXAgZGF0YQpsaWJyYXJ5KGdnbWFwKSAgICAgICAgICMgZm9yIG1hcHBpbmcgcG9pbnRzIG9uIG1hcHMKbGlicmFyeShncGxvdHMpICAgICAgICAjIGZvciBjb2wyaGV4KCkgZnVuY3Rpb24KbGlicmFyeShSQ29sb3JCcmV3ZXIpICAjIGZvciBjb2xvciBwYWxldHRlcwpsaWJyYXJ5KHNmKSAgICAgICAgICAgICMgZm9yIHdvcmtpbmcgd2l0aCBzcGF0aWFsIGRhdGEKbGlicmFyeShsZWFmbGV0KSAgICAgICAjIGZvciBoaWdobHkgY3VzdG9taXphYmxlIG1hcHBpbmcKbGlicmFyeShnZ3RoZW1lcykgICAgICAjIGZvciBtb3JlIHRoZW1lcyAoaW5jbHVkaW5nIHRoZW1lX21hcCgpKQpsaWJyYXJ5KHBsb3RseSkgICAgICAgICMgZm9yIHRoZSBnZ3Bsb3RseSgpIC0gYmFzaWMgaW50ZXJhY3Rpdml0eQpsaWJyYXJ5KGdnYW5pbWF0ZSkgICAgICMgZm9yIGFkZGluZyBhbmltYXRpb24gbGF5ZXJzIHRvIGdncGxvdHMKbGlicmFyeShzaGlueSkgICAgICAgICAjIGZvciBjcmVhdGluZyBpbnRlcmFjdGl2ZSBhcHBzCmdzNF9kZWF1dGgoKSAgICAgICAgICAgIyBUbyBub3QgaGF2ZSB0byBhdXRob3JpemUgZWFjaCB0aW1lIHlvdSBrbml0Lgp0aGVtZV9zZXQodGhlbWVfbWluaW1hbCgpKQpgYGAKCmBgYHtyIG15X2xpYnJhcmllcywgaW5jbHVkZT1GQUxTRX0KIyBMaXNhIG5lZWRzIHRoaXMsIHN0dWRlbnRzIGRvbid0CmxpYnJhcnkoZG93bmxvYWR0aGlzKSAjIGZvciBpbmNsdWRpbmcgZG93bmxvYWQgYnV0dG9ucyBmb3IgZmlsZXMKbGlicmFyeShmbGFpcikgIyBmb3IgaGlnaGxpZ2h0aW5nIGNvZGUKYGBgCgpgYGB7ciBkYXRhfQojIExpc2EncyBnYXJkZW4gZGF0YQpnYXJkZW5faGFydmVzdCA8LSByZWFkX3NoZWV0KCJodHRwczovL2RvY3MuZ29vZ2xlLmNvbS9zcHJlYWRzaGVldHMvZC8xRGVrU2F6Q3pLcVBTMmpuR2hLdWU3dEx4UlUzR1ZMMW94aS00YkVNNUlXdy9lZGl0P3VzcD1zaGFyaW5nIikgJT4lIAogIG11dGF0ZShkYXRlID0geW1kKGRhdGUpKQpgYGAKCiMjIExlYXJuaW5nIEdvYWxzCgpBZnRlciB0aGlzIHR1dG9yaWFsLCB5b3Ugc2hvdWxkIGJlIGFibGUgdG8gZG8gdGhlIGZvbGxvd2luZzoKCiogQWRkIGJhc2ljIGludGVyYWN0aXZpdHkgdG8gYSBgZ2dwbG90MmAgcGxvdCB1c2luZyBgZ2dwbG90bHkoKWAuICAKCiogQWRkIGFuaW1hdGlvbiBsYXllcnMgdG8gcGxvdHMgdXNpbmcgYGdnYW5pbWF0ZWAgZnVuY3Rpb25zLiAgCgoqIENyZWF0ZSBhIHNoaW55IGFwcCB0aGF0IHJlcXVpcmVzIGlucHV0cy4gIAoKKiBQdWJsaXNoIGEgc2hpbnkgYXBwIHRvIHNoaW55YXBwcy5pby4KCiMjICBNb3RpdmF0aW9uCgojIyBFYXN5IGludGVyYWN0aXZpdHkgd2l0aCBgcGxvdGx5YAoKUHJvYmFibHkgdGhlIGVhc2llc3Qgd2F5IHRvIGFkZCBpbnRlcmFjdGl2aXR5IHRvIGEgcGxvdCBjcmVhdGVkIHdpdGggYGdncGxvdDJgIGlzIGJ5IHVzaW5nIHRoZSBgZ2dwbG90bHkoKWAgZnVuY3Rpb24gZnJvbSB0aGUgYHBsb3RseWAgbGlicmFyeS4gVGhlIGBwbG90bHlgIHBhY2thZ2UgY2FuIGRvIEEgTE9UIG1vcmUgdGhhbiB3aGF0IHdlJ2xsIGNvdmVyIGluIHRoaXMgY291cnNlIGFzIGl0IGlzIGEgcGxvdHRpbmcgZnJhbWV3b3JrIGlmIGl0cyBvd24uIEJ1dCwgaXQgY2FuIGRvIGEgbG90IHdpdGgganVzdCB0aGF0IG9uZSBmdW5jdGlvbi4gCgpMZXQncyBsb29rIGF0IGFuIGV4YW1wbGUuIEluIHRoZSBjb2RlIGJlbG93LCBJIGNvbXB1dGUgdGhlIGN1bXVsYXRpdmUgaGFydmVzdCBpbiBwb3VuZHMgYnkgdmVnZXRhYmxlIGFuZCBjcmVhdGUgYSBiYXIgZ3JhcGguICBJIHNhdmUgdGhlIGdyYXBoIGFuZCBwcmludCBpdCBvdXQuIFRoZSBjb2RlIGFuZCBncmFwaCBzaG91bGQgYmUgZmFtaWxpYXIuCgpgYGB7cn0KdmVnZ2llX2hhcnZlc3RfZ3JhcGggPC0gZ2FyZGVuX2hhcnZlc3QgJT4lIAogIGdyb3VwX2J5KHZlZ2V0YWJsZSkgJT4lIAogIHN1bW1hcml6ZSh0b3RhbF93dF9sYnMgPSBzdW0od2VpZ2h0KSowLjAwMjIwNDYyKSAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21fY29sKGFlcyh4ID0gdG90YWxfd3RfbGJzLCAKICAgICAgICAgICAgICAgeSA9IGZjdF9yZW9yZGVyKHZlZ2V0YWJsZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b3RhbF93dF9sYnMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLmRlc2MgPSBGQUxTRSkpKSArCiAgbGFicyh0aXRsZSA9ICJUb3RhbCBIYXJ2ZXN0IGJ5IHZlZ2V0YWJsZSAobGIpIiwgCiAgICAgICB4ID0gIiIsCiAgICAgICB5ID0gIiIpCgp2ZWdnaWVfaGFydmVzdF9ncmFwaApgYGAKCk5vdywgd2UgYHBsb3R5YC1pZnkgaXQhCgpgYGB7cn0KZ2dwbG90bHkodmVnZ2llX2hhcnZlc3RfZ3JhcGgpCmBgYAoKVGhlIGxhYmVsaW5nIGlzIGZhaXJseSB1Z2x5IGluIHRoZSBncmFwaCBhYm92ZS4gSSBjYW4gZml4IHNvbWUgb2YgdGhhdCBieSBlZGl0aW5nIG15IG9yaWdpbmFsIHBsb3QuIEluIHRoZSBjb2RlIGJlbG93LCBJIGFkZCBhIGB0ZXh0YCBhZXN0aGV0aWMsIHdoaWNoIHdpbGwgYmUgdXNlZCBpbiBgZ2dwbG90bHkoKWAgdG8gZGlzcGxheSB0aGUgdmVnZXRhYmxlIG5hbWUsIGFuZCB1c2UgYHRvb2x0aXBgIHRvIHRlbGwgaXQgdGhlIGFlc3RoZXRpY3MgdG8gZGlzcGxheSB3aGVuIHNjcm9sbGluZyBvdmVyIHRoZSBncmFwaC4KCmBgYHtyIGdncGxvdGx5LWV4LCBlY2hvPUZBTFNFLCBldmFsPUZBTFNFfQp2ZWdnaWVfaGFydmVzdF9ncmFwaDIgPC0gZ2FyZGVuX2hhcnZlc3QgJT4lIAogIGdyb3VwX2J5KHZlZ2V0YWJsZSkgJT4lIAogIHN1bW1hcml6ZSh0b3RhbF93dF9sYnMgPSBzdW0od2VpZ2h0KSowLjAwMjIwNDYyKSAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21fY29sKGFlcyh4ID0gdG90YWxfd3RfbGJzLCAKICAgICAgICAgICAgICAgeSA9IGZjdF9yZW9yZGVyKHZlZ2V0YWJsZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b3RhbF93dF9sYnMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLmRlc2MgPSBGQUxTRSksCiAgICAgICAgICAgICAgIHRleHQgPSB2ZWdldGFibGUpKSArCiAgbGFicyh0aXRsZSA9ICJUb3RhbCBIYXJ2ZXN0IGJ5IHZlZ2V0YWJsZSAobGIpIiwgCiAgICAgICB4ID0gIiIsCiAgICAgICB5ID0gIiIpCgpnZ3Bsb3RseSh2ZWdnaWVfaGFydmVzdF9ncmFwaDIsCiAgICAgICAgIHRvb2x0aXAgPSBjKCJ0ZXh0IiwgIngiKSkKYGBgCgoKYGBge3IsIGVjaG89RkFMU0V9CmRlY29yYXRlX2NodW5rKCJnZ3Bsb3RseS1leCIpICU+JSAKICBmbGFpcigidGV4dCA9IHZlZ2V0YWJsZSIpICU+JSAKICBmbGFpcigidG9vbHRpcCA9IikKYGBgCgoKVGhpcyB3b3JrcyBmb3IgbWFueSBkaWZmZXJlbnQgdHlwZXMgb2YgcGxvdHMgY3JlYXRlZCB3aXRoIGBnZ3Bsb3QyYC4gCgojIyMgUmVzb3VyY2VzCgpbUGxvdHkncyBnZ3Bsb3QyIGludGVncmF0aW9uXShodHRwczovL3Bsb3RseS5jb20vZ2dwbG90Mi8pCgojIyMgWW91ciB0dXJuIQoKIyMjIyBFeGVyY2lzZQoKSW4gdGhpcyBleGVyY2lzZSwgY2hvb3NlIDIgZ3JhcGhzIHlvdSBoYXZlIGNyZWF0ZWQgZm9yIEFOWSBhc3NpZ25tZW50IGluIHRoaXMgY2xhc3MgYW5kIGFkZCBpbnRlcmFjdGl2aXR5IHVzaW5nIHRoZSBgZ2dwbG90bHkoKWAgZnVuY3Rpb24uIAoKIyMgQWRkaW5nIGFuaW1hdGlvbiB3aXRoIGBnZ2FuaW1hdGVgCgojIyMgS2V5IGZ1bmN0aW9ucwoKVGhlIGBnZ2FuaW1hdGVgIHBhY2thZ2Ugd29ya3Mgd2VsbCB3aXRoIGBnZ3Bsb3QyYCBmdW5jdGlvbnMgYnkgcHJvdmlkaW5nIGFkZGl0aW9uYWwgZ3JhbW1hciB0aGF0IGFzc2lzdHMgaW4gYWRkaW5nIGFuaW1hdGlvbiB0byB0aGUgcGxvdHMuIFRoZXNlIGZ1bmN0aW9ucyBnZXQgYWRkZWQgYXMgbGF5ZXJzIGluIGBnZ3Bsb3QoKWAsIGp1c3QgbGlrZSB5b3UgYXJlIHVzZWQgdG8gYWRkaW5nIGBnZW9tXyooKWAgbGF5ZXJzIGFuZCBvdGhlciBsYXllcnMgdGhhdCBtb2RpZnkgdGhlIGdyYXBoLgoKRnJvbSBUaG9tYXMgUGVkZXJzZW4ncyBkb2N1bWVudGF0aW9uLCBoZXJlIGFyZSB0aGUga2V5IGZ1bmN0aW9ucy9ncmFtbWFyIG9mIHRoZSBwYWNrYWdlOgoKKiBgdHJhbnNpdGlvbl8qKClgIGRlZmluZXMgaG93IHRoZSBkYXRhIHNob3VsZCBiZSBzcHJlYWQgb3V0IGFuZCBob3cgaXQgcmVsYXRlcyB0byBpdHNlbGYgYWNyb3NzIHRpbWUgKHRpbWUgaXMgbm90IGFsd2F5cyBhY3R1YWwgdGltZSkuCiogYHZpZXdfKigpYCBkZWZpbmVzIGhvdyB0aGUgcG9zaXRpb25hbCBzY2FsZXMgc2hvdWxkIGNoYW5nZSBhbG9uZyB0aGUgYW5pbWF0aW9uLgoqIGBzaGFkb3dfKigpYCBkZWZpbmVzIGhvdyBkYXRhIGZyb20gb3RoZXIgcG9pbnRzIGluIHRpbWUgc2hvdWxkIGJlIHByZXNlbnRlZCBpbiB0aGUgZ2l2ZW4gcG9pbnQgaW4gdGltZS4KKiBgZW50ZXJfKigpL2V4aXRfKigpYCBkZWZpbmVzIGhvdyBuZXcgZGF0YSBzaG91bGQgYXBwZWFyIGFuZCBob3cgb2xkIGRhdGEgc2hvdWxkIGRpc2FwcGVhciBkdXJpbmcgdGhlIGNvdXJzZSBvZiB0aGUgYW5pbWF0aW9uLgoqIGBlYXNlX2FlcygpYCBkZWZpbmVzIGhvdyBkaWZmZXJlbnQgYWVzdGhldGljcyBzaG91bGQgYmUgZWFzZWQgZHVyaW5nIHRyYW5zaXRpb25zLgoKWW91IG9ubHkgbmVlZCBhIGB0cmFuc2l0aW9uXyooKWAgb3IgYHZpZXdfKigpYCBmdW5jdGlvbiB0byBhZGQgYW5pbWF0aW9uLiBUaGlzIHR1dG9yaWFsIGZvY3VzZXMgb24gdGhyZWUgYHRyYW5zaXRpb25fKigpYCBmdW5jdGlvbnM6IGB0cmFuc2l0aW9uX3N0YXRlcygpYCwgYHRyYW5zaXRpb25fdGltZSgpYCwgYW5kIGB0cmFuc2l0aW9uX3JldmVhbCgpYC4gCgojIyMgYHRyYW5zaXRpb25fKigpYCBmdW5jdGlvbnMKClRoZSBmb2xsb3dpbmcgaW1hZ2UsIHRha2VuIGZyb20gdGhlIGdnYW5pbWF0ZSBjaGVhdHNoZWV0LCBnaXZlcyBhIG5pY2Ugb3ZlcnZpZXcgb2YgdGhlIHRocmVlIGZ1bmN0aW9ucy4gCgohW0ltYWdlIGZyb206IGh0dHBzOi8vdWdvcHJvdG8uZ2l0aHViLmlvL3Vnb19yX2RvYy9wZGYvZ2dhbmltYXRlLnBkZl0oLi4vLi4vaW1hZ2VzL2dnYW5pbWF0ZV90cmFuc2l0aW9ucy5wbmcpCgoKIyMjIERlbW8gdmlkZW8KCiMjIyBSZXNvdXJjZXMKCiogW2dnYW5pbWF0ZSBpbnRybyBzbGlkZXNdKGh0dHBzOi8vZ29vZGVrYXQuZ2l0aHViLmlvL3ByZXNlbnRhdGlvbnMvMjAxOS1pc3VnZy1nZ2FuaW1hdGUtc3Bvb2t5L3NsaWRlcy5odG1sKSBieSBLYXRoZXJpbmUgR29vZGUgKHNoZSBhbmltYXRlcyBiYXRzIGZseWluZyEpCgoqIFtnZ2FuaW1hdGUgY2hlYXRzaGVldF0oaHR0cHM6Ly91Z29wcm90by5naXRodWIuaW8vdWdvX3JfZG9jL3BkZi9nZ2FuaW1hdGUucGRmKQoKKiBbZ2dhbmltYXRlIGJ5IFRob21hcyBQZWRlcnNlbl0oaHR0cHM6Ly9naXRodWIuY29tL3Rob21hc3A4NS9nZ2FuaW1hdGUpIC0gc2Nyb2xsIGRvd24gdG8gdGhlIGJvdHRvbSAgCiogW1BlZGVyc2VuIGludHJvZHVjdG9yeSB2aWduZXR0ZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2dnYW5pbWF0ZS92aWduZXR0ZXMvZ2dhbmltYXRlLmh0bWwpIC0gZ2l2ZXMgYSBicmllZiBpbnRybyB0byB3aGF0IGVhY2ggb2YgdGhlIGtleSBmdW5jdGlvbnMgZG8gIAoqIFtnZ2FuaW1hdGUgd2lraSBwYWdlXShodHRwczovL2dpdGh1Yi5jb20vdGhvbWFzcDg1L2dnYW5pbWF0ZS93aWtpKSAtIG1vc3Qgb2YgdGhpcyBpcyBjdXJyZW50bHkgdW5kZXIgZGV2ZWxvcG1lbnQgYnV0IHRoZXJlJ3Mgc29tZSBnb29kIGV4YW1wbGVzICAKCiogW3JvcGVuc2NpIGV4YW1wbGVzXShodHRwczovL2dpdGh1Yi5jb20vcm9wZW5zY2lsYWJzL2xlYXJuZ2dhbmltYXRlKQoKIyMjIFlvdXIgdHVybiEKCiMjIENyZWF0aW5nIGFuIGFwcCB3aXRoIGBzaGlueWAKCiMjIyBEZW1vIHZpZGVvCgojIyMgUmVzb3VyY2VzCgojIyMgWW91ciB0dXJuIQ==